/*
 * Copyright 2010 Brian Watkins
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package bw.os;

import java.util.*;
import bw.util.*;

public class ExpiringHashtable extends HashMap implements MemoryCache
{
    private String _name = null;
    private long _defaultTimeout;
    private ExpiringHashtableMonitor _monitor = null;
    private Object _lock = new Object();

    public ExpiringHashtable ( String name, long timeout )
    {
        _defaultTimeout = timeout;
        _name = name;
        _monitor = new ExpiringHashtableMonitor();
    }

    public void put ( String key, Object obj, long timeout )
    {
        synchronized ( _lock )
        {
            super.put( key, new ExpiringObject( obj, timeout ) );
        }
    }

    public void put ( String key, Object obj )
    {
        put( key, obj, _defaultTimeout );
    }

    public Object get ( String key )
    {
        Object obj = null;

        synchronized ( _lock )
        {
            ExpiringObject exObj = getExpiringObject( key );
            if ( exObj != null )
            {
                //Log.getInstance().write( "EHM: " + key + " is touched!" );
                //do this extra check because the computer could have been asleep
                //while some objects timed out. this ensures consistency. so when
                //we wake up they will be expired no matter if some other thread tries
                //to request them before the monitor thread wakes up and expires them
                if ( exObj.expired() )
                {
                    remove( key );
                }
                else
                {
                    exObj.touch();
                    obj = exObj.object;
                }
            }
        }

        return obj;
    }

    public Object remove ( String key )
    {
        return super.remove( key );
    }

    public String getName () { return _name; }

    private ExpiringObject getExpiringObject ( String key )
    {
        return (ExpiringObject) super.get( key );
    }

    public void start ()
    {
        _monitor.start();
    }

    public void shutdown ()
    {
        _monitor.shutdown();
    }

    class ExpiringObject
    {
        protected Object object = null;
        protected long lastTouched = 0L;
        protected long timeout = 0L;

        public ExpiringObject ( Object obj, long timeout )
        {
            this.object = obj;
            this.lastTouched = System.currentTimeMillis();
            this.timeout = timeout;
        }

        public void touch ()
        {
            lastTouched = System.currentTimeMillis();
        }

        public boolean expired ()
        {
            return ( ( System.currentTimeMillis() - this.lastTouched ) > this.timeout );
        }
    }
    
    class ExpiringHashtableMonitor extends Agent
    {
        public ExpiringHashtableMonitor ()
        {
            super( _name + "_Monitor", 60 * 1000L );
        }

        public void performAction ()
        {
            synchronized ( _lock )
            {
                Iterator keys = keySet().iterator();
                while ( keys.hasNext() )
                {
                    String key = (String) keys.next();
                    
                    ExpiringObject exObj = getExpiringObject( key );
                    
                    if ( exObj.expired() )
                    {
                        keys.remove();
                    }
                }
            }

        }
    }
}